import os
import json
import time
import socket
import curses

from shared.colors import Color
from shared.monitor import Monitor


class Status:
	UNKNOWN = 0
	MIGRATING = 1
	DONE = 2
	ERROR = 3

	@classmethod
	def get_text(cls, status):
		"""Return test representation of status constant"""
		strings = {
				Status.UNKNOWN: 'Unknown',
				Status.MIGRATING: 'Migrating',
				Status.DONE: 'Done',
				Status.ERROR: 'Error'
			}

		return strings[status]

	@classmethod
	def get_color(cls, status):
		"""Return color string for status constant"""
		colors = {
				Status.UNKNOWN: Color.UNKNOWN,
				Status.MIGRATING: Color.WORKING,
				Status.DONE: Color.GOOD,
				Status.ERROR: Color.BAD
			}

		return colors[status]


class Domain:
	"""Domain container class."""

	def __init__(self, domain, expected_address):
		self._domain_name = domain
		self._expected_address = expected_address
		self._current_address = ''
		self._status = Status.UNKNOWN

	def check(self):
		"""Update domain address"""
		try:
			self._current_address = socket.gethostbyname(self._domain_name)
			self._status = (
					Status.MIGRATING, 
					Status.DONE
				)[self._current_address == self._expected_address]

		except:
			self._status = Status.ERROR

	def get_name(self):
		"""Return domain name"""
		return self._domain_name

	def get_expected_address(self):
		"""Return expected IP address for domain"""
		return self._expected_address

	def get_current_address(self):
		"""Return current IP address for domain"""
		return self._current_address

	def get_status(self):
		"""Return status of domain check"""
		return self._status


class DnsMonitor(Monitor):
	"""DNS migration monitor class. This class is used to check on IP address
	of specified domains and verify them on regular intervals.

	"""
	
	def __init__(self, window, config_file):
		Monitor.__init__(self, window, config_file)

		self._window_size = self._window.getmaxyx()
		self._container = self._window.derwin(self._window_size[0] - 1, self._window_size[1], 1, 0)
		self._container_size = self._container.getmaxyx()

		self._offset = 0
		self._direction = 1
		self._domains = []
		self._config_time = 0
		self._config_file = Monitor.get_config_filename('dns_monitor.json')


	def run(self):
		"""Infinite loop of domain address check"""
		Monitor.run(self)

		# load domains
		self._check_config_changes()

		# update domain statuses
		map(lambda domain: domain.check(), self._domains)

		# create header
		self._update_window()

		# enter main loop
		counter = 0
		while self._thread_active.is_set():
			# check if configuration file was changed
			self._check_config_changes()

			# update domains
			counter += 1
			if counter == 10:
				map(lambda domain: domain.check(), self._domains)
				counter = 0

			# update screen
			if self._visible:
				self._update_container()

			# sleep for a while
			time.sleep(1)

	def _check_config_changes(self):
		"""Check if configuration file was changed"""
		stat = os.stat(self._config_file)

		# (re)load config if times differ
		if self._config_time != stat.st_mtime:
			self._load_domains()
			self._config_time = stat.st_mtime

	def _load_domains(self):
		"""Load domains from argument list"""
		data = []
		decoder = json.JSONDecoder()

		self._offset = 0
		self._direction = 1
		self._domains = []

		# load JSON and parse it
		if os.path.exists(self._config_file):
			with open(self._config_file, 'r') as dns_data:
				data = decoder.decode(dns_data.read())

		# create domains
		for domain_data in data:
			domain_name = domain_data['domain']
			expected_address = domain_data['expected_address']

		 	self._domains.append(Domain(domain_name, expected_address))

	def _update_window(self):
		"""Print whole window"""
		self._window.bkgd(curses.color_pair(Color.WINDOW))
		self._container.bkgd(curses.color_pair(Color.CONTAINER))

		# print column names
		self._window.addstr(0, 0, 'Domain', curses.color_pair(Color.WINDOW) | curses.A_BOLD)
		self._window.addstr(0, 37, 'Expected', curses.color_pair(Color.HEADER))
		self._window.addstr(0, 54, 'Current', curses.color_pair(Color.HEADER))
		self._window.addstr(0, 71, 'Status', curses.color_pair(Color.HEADER))

		# update container
		self._update_container()

		# mark window as changed
		self._window.noutrefresh()

	def _update_container(self):
		"""Print statuses of all domains being monitored"""
		sub_data = self._domains[self._offset : self._offset + self._container_size[0]]

		# update offset
		if len(self._domains) > self._container_size[0]:
			self._offset += self._direction
			if self._offset + self._container_size[0] > len(self._domains) and self._direction == 1:
				self._offset = len(self._domains) - self._container_size[0] - 1
				self._direction = -1

			elif self._offset - 1 < 0 and self._direction == -1:
				self._offset = 0
				self._direction = 1

		# draw data
		for index, domain in enumerate(sub_data, 0):
			status = domain.get_status()
			self._container.addstr(index, 0, domain.get_name().ljust(35))
			self._container.addstr(index, 37, domain.get_expected_address().ljust(15))
			self._container.addstr(index, 54, domain.get_current_address().ljust(15))
			self._container.addstr(
							index, 71,
							Status.get_text(status).ljust(10),
							curses.color_pair(Status.get_color(status))
						)

		# mark container as updated
		self._container.noutrefresh()

	def show(self):
		"""Show monitor windows"""
		Monitor.show(self)
		self._update_window()
		self._update_container()

	def hide(self):
		"""Hide monitor windows"""
		Monitor.hide(self)
